import logging

from django.db.models import Q
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import viewsets, status
from erp_system.models import MenuModel, PermissionsModel  # 只有模型类才这样导包，因为前面定义了导包路径
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from MSB_ERP.apps.erp_system.serializer.menu_serializer import MenuSerializer

# 得到日志输出对象logger（因为写的有）
logger = logging.getLogger('erp') # 因为配置文件中只配置了erp和djiang的，所以只能选两个

'''
功能菜单的模型类：
1.新增
2.查询单个功能菜单
3.查询所有功能菜单
4.查询某一个父菜单下的所有子菜单列表
5.查询顶级菜单列表
6.删除某一个功能菜单
7.批量的删除多个功能菜单，传参：ids=[1,2,3]
8.修改功能菜单
'''
class MenuView(viewsets.ModelViewSet):
    '''
    create:
    新增功能菜单，

    参数：为Menu对象，其中delete_flag，catate_time，updata_time不用传参，return：返回添加后的对象

    retrieve:
    查询单个菜单对象

    list:
    查询所有的菜单

    如果参数中有pid，则查询某一个父菜单下的所有子菜单列表，pid=0表示查询顶级菜单

    update:
    修改菜单

    partial_update:
    局部修改菜单，修改任意指定的属性

    destroy:
    删除指定id的菜单

    get_menus_by_permission:
    当前登录用户，根据权限插叙那些，拥有get权限的功能菜单列表

    功能菜单列表信息，status：200（成功），return：功能菜单信息列表（返回是树形的菜单）
    '''
    # 用户的认证
    # permission_classes = [IsAuthenticated]

    # 查询所有没有删除标记的（因为标记为1的时候就是被删除了）
    queryset = MenuModel.objects.filter(delete_flag=0).all()
    serializer_class = MenuSerializer
    '''
    默认行为：queryset 属性提供了一个默认的查询集，即使你没有重写 get_queryset 方法，视图集也能正常工作。
    动态行为：get_queryset 方法允许你根据请求的不同动态地调整查询集。例如，根据 pid 参数的不同，返回不同的查询集。
    '''
    # 第一个要求和第二个要求父类可以满足，不用重写

    # 重写Query（完成了3~5的要求）
    # get_queryset没调用，就默认调用前面那个
    '''' 写两个的返回数据的话：这个主要是为了程序的统一，一般都要写一个默认的数据集 '''
    def get_queryset(self):
        logger.info(f'当前登录用户是：{self.request.user.username}')
        '''
        两种情况：
        1.查询所有菜单列表，不要传任何参数
        2.查询某一个父菜单下的所有子菜单列表，需要传递参数，当pid==0时，查询所有顶级菜单列表
        '''
        query_param = self.request.query_params.get('pid',None)
        if query_param:
            pid = int(query_param)
            if pid == 0: # 查询顶级菜单列表
                # parser__isnull如果没有东西的话，表示没有父菜单的意思
                return MenuModel.objects.filter(parent__isnull=True,delete_flag=0).all()
            else: # 查询某一个父菜单下的所有子菜单列表
                return MenuModel.objects.filter(parent__id=pid,delete_flag=0).all()
        else:
            # 查询所有没有被删除的列表
            return MenuModel.objects.filter(delete_flag=0).all()

    # 完成了6的要求
    # 因为父类里面的函数，会把数据删除的，所以不能用父类的函数
    def destroy(self, request, *args, **kwargs):
        # 修改一个删除的标记为：delete_flasg=1
        menu = self.get_object() # 拿到当前对象
        menu.delete_flag = '1' # 修改属性值
        menu.save()
        # 可能该菜单下面还有很多子菜单，这些子菜单也要修改属性
        MenuModel.objects.filter(parent__id=menu.id).update(delete_flag=1) # 查询所有的子菜单列表并修改
        return Response(status=status.HTTP_204_NO_CONTENT) # 返回值

    # 完成了7的要求
    ''' 因为这个是特别的接口文档所以要定义del_ids给swagger_auto_schema用的 '''
    # TYPE_OBJECT传的整一个参数是个JSON对象，required必须传一个什么参数（这个参数一定要跟真正的参数要一样，还有其他的话逗号隔开就行）
    # required必传选项的内容是什么
    # properties定义一下整个JSON对象，他里面的参数具体是什么类型的
    del_ids = openapi.Schema(type=openapi.TYPE_OBJECT,required=['ids'],properties={
        # 参数名：接一个类型（类型一个是openapi.Schema开头的）
        # type：数据类型
        # 因为他是整数，所以要openapi.TYPE_INTEGER整数类型
        # description对字段的描述
        # items定义数组元素的类型
        'ids': openapi.Schema(type=openapi.TYPE_ARRAY,items=openapi.Schema(type=openapi.TYPE_INTEGER),description='选择那些需要删除（主键）的列表')
    })
    '''
    action装饰器可以接收两个参数：
    methods：声明该action对应的请求方法，列表传递
    detail：声明该action的路径是否与单一资源对应，及是否是xxx/<pk>/action方法名/
        True：表示路径是xxx/<pk>/action方法名/
        False:表示路径格式是xxx/action方法名/
    '''
    # 这个是特别的接口文档，methods表示请求的方法，request_body表示指定传参
    @swagger_auto_schema(method='delete',request_body=del_ids,operation_description='批量删除') # 装饰器一定要写在action的上面
    @action(methods=['delete'],detail=False)
    def multiple_delete(self, request, *args, **kwargs):
        delete_ids = request.data.get('ids') # 拿到数据
        if not delete_ids:
            return Response(
                data={'delail':'参数错误，ids是必传参数'},
                status=status.HTTP_400_BAD_REQUEST # 返回状态
            )
        elif not isinstance(delete_ids,list): # 判断是方是list类型的
            return Response(
                data = {'detail': 'ids格式错误,必须为List'},
                status = status.HTTP_400_BAD_REQUEST
            )

        # 先删除传递过来的菜单
        MenuModel.objects.filter(id__in=delete_ids).update(delete_flag=1)
        # 后删除所有的子菜单
        for m_id in delete_ids:
            MenuModel.objects.filter(parent__id=m_id).update(delete_flag=1)  # 查询所有的子菜单列表并修改
        return Response(status=status.HTTP_204_NO_CONTENT) # 返回值
    # 第八点父类已经完全可以提供了，所以不用重写

    ''' 当前登录用户，根据权限查询那些，拥有get权限的功能菜单列表 '''
    @action(methods=['get'], detail=False)
    def get_menus_by_permission(self,request,*args,**kwargs):

        # 第一步：查询当前登录用户，拥有的权限ID列表（得到当前登录用户所拥有的权限ID列表）
        # 只要用户登录了，他的ID就在request.user里面
        # roles代表用户所拥有的所有角色
        # 注意：values_list返回的是列表，values返回的是字典（字段名作为键）
        permission_ids = request.user.roles.values_list('permissions',flat=True).distinct()

        # 第二步：查询拥有get权限的功能菜单的ID列表，两个filter是一个并且的关系
        # 注意：特殊情况，父菜单没有任何methods，所以如果是父菜单的权限也应该要出现
        # # 要不他的请求方式是get，要不他是父菜单
        menu_ids = PermissionsModel.objects.filter(id__in=permission_ids)\
                                            .filter(Q(method='GET') | Q(is_menu=True))\
                                            .values_list('menu',flat=True) # 返回出去的数据是外键的数据

        # 第三步：就跟ID查询菜单列表（逗号表示并且的意思），列表里面是对象
        menu_list = MenuModel.objects.filter(id__in=menu_ids,delete_flag=0).all()

        ''' 知识点：上面的查询条件这么复杂。查询条件这么多，是不是每一步都变成SQL语句？
            答：不是的，他最终会合成一条SQL语句，Django当中的ORM框架他默认就是懒加载的
            （第一步执行查到一个permission_ids，但这个permission_ids如果没有封装到respon，或者print出来的话，
            他是没有值的，他不会生成SQL语句去数据库中查询的，他会继续执行查询menu_ids的语句中，他没有返回出去和打印，
            他就是继续执行menu_list的查询，最后是到了下面的for循环才用到了他）
        '''
        # 通过serilizer把对象变成字典
        serializer = MenuSerializer(instance=menu_list,many=True)

        # 变成一个树形结构
        tree_dict = {} # 当父菜单
        tree_data = []
        for item in serializer.data:
            print(item['id'])
            # 给字典中的键赋值，如果没有这个键，就会添加一个
            tree_dict[item['id']] = item # 他赋值的时候会自动找到该值的数据进行赋值
        #print(tree_dict)
        for i in tree_dict:
            if tree_dict[i]['parent']:
                pid = tree_dict[i]['parent']
                panent = tree_dict[pid]
                panent.setdefault('children',[]).append(tree_dict[i])
            else: # 父菜单
                tree_data.append(tree_dict[i])

        return Response(tree_data)




